from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Union, List
from .token import Token


class ExprVisitor(ABC):
    @abstractmethod
    def visit_binary(self, expr: 'ExprBinary'):
        """"""
    @abstractmethod
    def visit_grouping(self, expr: 'ExprGrouping'):
        """"""
    @abstractmethod
    def visit_literal(self, expr: 'ExprLiteral'):
        """"""
    @abstractmethod
    def visit_unary(self, expr: 'ExprUnary'):
        """"""
    @abstractmethod
    def visit_variable(self, expr: 'ExprVariable'):
        """"""
    @abstractmethod
    def visit_assign(self, expr: 'ExprAssign'):
        """"""
    @abstractmethod
    def visit_logical(self, expr: 'ExprLogical'):
        """"""
    @abstractmethod
    def visit_call(self, expr: 'ExprCall'):
        """"""
    @abstractmethod
    def visit_get(self, expr: 'ExprGet'):
        """"""
    @abstractmethod
    def visit_set(self, expr: 'ExprSet'):
        """"""
    @abstractmethod
    def visit_this(self, expr: 'This'):
        """"""
    @abstractmethod
    def visit_super(self, expr: 'Super'):
        """"""


class Expr(ABC):
    @abstractmethod
    def accept(self, visitor: 'ExprVisitor'):
        """"""


class ExprBinary(Expr):
    def __init__(self, left: 'Expr', op: Token, right: 'Expr'):
        self.left = left
        self.op = op
        self.right = right

    def __repr__(self):
        return f'Binary({self.left} {self.op.lexeme} {self.right}'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_binary(self)


class ExprGrouping(Expr):
    def __init__(self, expr: 'Expr'):
        self.expr = expr

    def __repr__(self):
        return f'Grouping({self.expr})'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_grouping(self)


class ExprLiteral(Expr):
    def __init__(self, value: Union[str, float, bool, None]):
        self.value = value

    def __repr__(self):
        return f'Literal({self.value})'

    def accept(self, visitor: 'ExprVisitor'):
        visitor.visit_literal(self)


class ExprUnary(Expr):
    def __init__(self, token: Token, expr: 'Expr'):
        self.token = token
        self.expr = expr

    def __repr__(self):
        return f'Unary({self.token.lexeme} {self.expr}'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_unary(self)


class ExprVariable(Expr):
    def __init__(self, name: Token):
        self.name = name

    def __repr__(self):
        return f'VarExpr({self.name.lexeme})'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_variable(self)


class ExprAssign(Expr):
    def __init__(self, name: Token, expr: 'Expr'):
        self.name = name
        self.expr = expr

    def __repr__(self):
        return f'Assign({self.name.lexeme}={self.expr}'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_assign(self)


class ExprLogical(Expr):
    def __init__(self, left: 'Expr', op: Token, right: 'Expr'):
        self.left = self
        self.op = op
        self.right = right

    def __repr__(self):
        return f'Logical({self.left} {self.op.lexeme} {self.right})'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_logical(self)


class ExprCall(Expr):
    def __init__(self, callee: 'Expr', arguments: List['Expr']):
        self.callee = callee
        self.arguments = arguments

    def __repr__(self):
        return f'Call(callee={self.callee}, arguments={self.arguments})'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_call(self)


class ExprGet(Expr):
    def __init__(self, obj: 'Expr', name: Token):
        self.obj = obj
        self.name = name

    def __repr__(self):
        return f'Get(obj={self.obj}, name={self.name.lexeme})'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_get(self)


class ExprSet(Expr):
    def __init__(self, obj: 'Expr', name: Token, value: 'Expr'):
        self.obj = obj
        self.name = name
        self.value = value

    def __repr__(self):
        return f'Set({self.obj}.{self.name.lexeme}={self.value}'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_set(self)


class This(Expr):
    def __init__(self, token: Token):
        self.token = token

    def __repr__(self):
        return 'this'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_this(self)


class Super(Expr):
    def __init__(self, token: Token):
        self.token = token

    def __repr__(self):
        return 'super'

    def accept(self, visitor: 'ExprVisitor'):
        return visitor.visit_super(self)
